import numpy as np


# 根据要求的方差百分比，求出所需要的特征值的个数n
def percent2n(eigVals, percent):
    sortArray = np.sort(eigVals)  # 升序
    sortArray = sortArray[-1::-1]  # 逆转，即降序
    arraySum = sum(sortArray)
    tmp = 0
    num = 0
    for i in sortArray:
        tmp += i
        num += 1
        if tmp >= arraySum * percent:
            print("特征值个数为: ", num)
            return num


# 零均值化
def zeroMean(dataMat):
    meanVal = np.mean(dataMat, axis=0)  # 按列求均值，即求各个特征的均值
    newData = dataMat - meanVal
    return newData, meanVal


def pca(dataMat, percent=0.99):
    """
    :param dataMat:
    :param percent: 需要解释多少方差. 需要解释的越多,降维之后维度越大(lowDDataMat),但还原数据效果越好(reconMat)
    :return: lowDDataMat, reconMat
    """
    # 中心化后的newData, 以及中心化之前的均值meanVal
    newData, meanVal = zeroMean(dataMat)
    covMat = np.cov(newData, rowvar=False)  # 求协方差矩阵. 若rowvar为False,一列代表一个样本;为True,一行代表一个变量

    eigVals, eigVects = np.linalg.eig(np.mat(covMat))  # 求特征值和特征向量,特征向量是按列放的，即一列代表一个特征向量
    n = percent2n(eigVals, percent)  # 要达到percent的方差百分比，需要前n个特征向量
    eigValIndice = np.argsort(eigVals)  # 对特征值从小到大排序
    n_eigValIndice = eigValIndice[-1:-(n + 1):-1]  # 最大的n个特征值的下标
    n_eigVect = eigVects[:, n_eigValIndice]  # 最大的n个特征值对应的特征向量
    lowDDataMat = newData * n_eigVect  # 低维特征空间的数据
    reconMat = (lowDDataMat * n_eigVect.T) + meanVal  # 根据降维后的数据,重构原维度数据
    return lowDDataMat, reconMat
